unit uRailroadYard;

interface

uses
  Classes, ExtCtrls, Graphics, SysUtils, XMLDoc, XMLIntf,
  config, uList, uTrackItem, uTrackSegment,
  uRail, uSwitch, uSignalLight, uOperatorStation;

const
  PARSE_LOG_FILE_NAME = 'parseLog.txt';

  RAILROAD_YARD_FILE = 'railroadYard';

  NODE_ITEM_CONTAINER = 'trackItems';
  NODE_ITEM = 'item';

  NODE_SEGMENT_CONTAINER = 'trackSegments';
  NODE_SEGMENT = 'segment';

  NODE_TRAIN_ROUTE_CONTAINER = 'trainRoutes';
  NODE_TRAIN_ROUTE = 'route';
  NODE_TRAIN_ROUTE_STARTING_SIGNAL = 'startingSignal';
  NODE_TRAIN_ROUTE_ENDING_TRACK = 'endingTrack';

  ITEM_TYPE_RAIL = 'rail';
  ITEM_TYPE_SWITCH = 'switch';
  ITEM_TYPE_SIGNAL_LIGHT = 'signal';
  ITEM_TYPE_OPERATOR_STATION = 'operator';


type

  TRailroadYard = class
    private
      aRasterX, aRasterY: byte;
      aCanvas: TCanvas;

      aTrackSegments: TList;
      aTrackItems: TList;

      aTrackStatusTimer: TTimer;

      procedure LoadTrackSegments(paXmlFile: IXMLDocument; paParseLog: TStrings);
      procedure SetColors(paBackground, paForeground: TColor);
      procedure DrawItems;
      function  ConvertXCoordinateToRaster(var paValue: Cardinal): Cardinal;
      function  ConvertYCoordinateToRaster(var paValue: Cardinal): Cardinal;
      procedure OnTrackStatusTimer(paSender: TObject);

    public
      constructor Create(paCanvas: TCanvas);
      destructor  Destroy; override;

      procedure Draw;
      function  LoadFromFile(const paFileName: string; paCreateLogFile: boolean = false): boolean;

      //TODO: this should probably be private
      function  FindTrackItem(paItemId: Cardinal): TTrackItem;
      function  FindTrackSegment(paSegmentId: Cardinal): TTrackSegment;

      procedure LeftMouseClicked(paX, paY: Cardinal);
      procedure MiddleMouseClicked(paX, paY: Cardinal);
      procedure RightMouseClicked(paX, paY: Cardinal);

      function IsSegmentOccupied(paSegment: Cardinal): boolean;
  end;


implementation


procedure TRailroadYard.LoadTrackSegments(paXmlFile: IXMLDocument; paParseLog: TStrings);
var
  segmentContainerNode, segmentNode, itemNode: IXmlNode;
  newSegmentId, itemId: Cardinal;
  newSegment: TTrackSegment;
  trackItem: TTrackItem;
begin
  paParseLog.Add('Loading track segments...');
  segmentContainerNode := paXmlFile.DocumentElement.ChildNodes.FindNode(NODE_SEGMENT_CONTAINER);
  if segmentContainerNode <> nil then
  begin
    itemId := 0;
    newSegmentId := 0;

    segmentNode := segmentContainerNode.ChildNodes.FindNode(NODE_SEGMENT);

    while segmentNode <> nil do
    begin
      if segmentNode.NodeName = NODE_SEGMENT then
      begin
        try
          newSegmentId := strToInt(segmentNode.Attributes[ATR_ID]);
        except
          //skip segments with undefined or wrong id
          paParseLog.Add('  Invalid segment id: "' + segmentNode.Attributes[ATR_ID] + '". Segment skipped.');
          continue;
        end;

        newSegment := TTrackSegment.Create(newSegmentId, aCanvas);

        //load items which belong to this segment
        itemNode := segmentNode.ChildNodes.FindNode(NODE_ITEM);
        while itemNode <> nil do
        begin
          if itemNode.NodeName = NODE_ITEM then
          begin
            try
              itemId := strToInt(itemNode.Attributes[ATR_ID]);
            except
              //skip items with wrong or undefined id
              paParseLog.Add('  Invalid item id: "' + itemNode.Attributes[ATR_ID] + '" in segment ' + segmentNode.Attributes[ATR_ID] + '. Item skipped.');
              continue;
            end;

            trackItem := FindTrackItem(itemId);
            if trackItem <> nil then
              newSegment.AddItem(trackItem)
            else
              paParseLog.Add('  Item ' + itemNode.Attributes[ATR_ID] + ' defined in segment ' + segmentNode.Attributes[ATR_ID] + ' does not exist.');
          end;
          itemNode := itemNode.NextSibling;
        end;

        aTrackSegments.Add(newSegment);
      end;
      segmentNode := segmentNode.NextSibling;
    end; //while

  end
  else begin
    paParseLog.Add('  Track segment definition is missing.');
  end;
end;


procedure TRailroadYard.SetColors(paBackground, paForeground: TColor);
begin
  aCanvas.Brush.Color := paBackground;
  aCanvas.Pen.Color := paForeground;
end;


procedure TRailroadYard.DrawItems;
var
  i: TListIterator;
  segment: TTrackSegment;
begin
  aCanvas.pen.color := COLOR_ITEM_DEFAULT;

 // aCanvas.Brush.Color := clGreen;

  //draw item backgrounds
  aCanvas.Pen.Width := 1;
  i := TListIterator.Create(aTrackItems);
  while i.HasNext do
  begin
    TTrackItem(i.Next).DrawBackGround;
  end;

  //draw items
  aCanvas.Pen.Width := 2;
  i.Reset;
  while i.HasNext do
  begin
    TTrackItem(i.Next).Draw;
  end;

  i.Destroy;
end;


function TRailroadYard.ConvertXCoordinateToRaster(var paValue: Cardinal): Cardinal;
begin
  result := round(paValue / aRasterX);
end;


function TRailroadYard.ConvertYCoordinateToRaster(var paValue: Cardinal): Cardinal;
begin
  result := round(paValue / aRasterY);
end;


constructor TRailroadYard.Create(paCanvas: TCanvas);
begin
  inherited Create;

  aRasterX := DEFAULT_RASTER_X;
  aRasterY := DEFAULT_RASTER_Y;

  aCanvas := paCanvas;
  aTrackItems := uList.TList.Create;
  aTrackSegments := uList.TList.Create;

  aTrackStatusTimer := TTimer.Create(nil);
  aTrackStatusTimer.Interval := 500;
  aTrackStatusTimer.OnTimer := OnTrackStatusTimer;
  aTrackStatusTimer.Enabled := true;
end;


procedure TRailroadYard.OnTrackStatusTimer(paSender: TObject);
begin

end;


destructor TRailroadYard.Destroy;
var
  item: TObject;
begin
  //destroy track segments
  while not aTrackSegments.IsEmpty do
  begin
    item := aTrackSegments.QueueOut;
    item.Destroy;
  end;
  aTrackSegments.Destroy;

  //destroy track items
  while not aTrackItems.IsEmpty do
  begin
    item := aTrackItems.QueueOut;
    item.Destroy;
  end;
  aTrackItems.Destroy;

  aTrackStatusTimer.Destroy;

  inherited Destroy;
end;


procedure TRailroadYard.Draw;
begin
  //clear canvas
  aCanvas.Brush.Color := COLOR_BACKGROUND;
  aCanvas.Rectangle(aCanvas.ClipRect);

  DrawItems;
end;


function TRailroadYard.LoadFromFile(const paFileName: string; paCreateLogFile: boolean): boolean;
var
  xmlFile: IXmlDocument;
  itemContainerNode, itemNode: IXmlNode;
  itemTypeStr: string;
  id: Cardinal;
  newTrackItem: TTrackItem;
  parseLog: TStringList;

  procedure SaveLog;
  begin
    if paCreateLogFile then
      parseLog.SaveToFile(PARSE_LOG_FILE_NAME);
  end;

begin
  parseLog := TStringList.Create;
  parseLog.Add('Loading file "' + paFileName + '"');

  try
    xmlFile := TXmlDocument.Create(paFileName);
    xmlFile.Options := xmlFile.Options - [doAttrNull]; //if attribute doesn't exist, return empty string
    xmlFile.Active := true;
  except
    parseLog.Add('Error opening file.');
    result := false;
    SaveLog;
    exit;
  end;

  //if its not a track definition file, exit
  if xmlFile.DocumentElement.NodeName <> RAILROAD_YARD_FILE then
  begin
    parseLog.Add('This is not a railroad yard definition file.');
    result := false;
    SaveLog;
    exit;
  end;

  //load items
  parseLog.Add('Loading track items...');
  itemContainerNode := xmlFile.DocumentElement.ChildNodes.FindNode(NODE_ITEM_CONTAINER);
  if itemContainerNode <> nil then
  begin
    itemNode := itemContainerNode.ChildNodes.FindNode(NODE_ITEM);
    while itemNode <> nil do
    begin
      if itemNode.NodeName = NODE_ITEM then
      begin
        try
          id := strToInt(itemNode.Attributes[ATR_ID]);
        except
          //skip items with undefined or wrong id
          parseLog.Add('  Invalid item id: "' + itemNode.Attributes[ATR_ID] + '". Item skipped.');
          continue;
        end;

        itemTypeStr := itemNode.Attributes[ATR_TYPE];

        //rail
        if itemTypeStr = ITEM_TYPE_RAIL then
        begin
          newTrackItem := TRail.Create(aCanvas, aRasterX, aRasterY, id);
        end

        //switch
        else if itemTypeStr = ITEM_TYPE_SWITCH then
        begin
          newTrackItem := TSwitch.Create(aCanvas, aRasterX, aRasterY, id);
        end

        //signal light
        else if itemTypeStr = ITEM_TYPE_SIGNAL_LIGHT then
        begin
          newTrackItem := TSignalLight.Create(aCanvas, aRasterX, aRasterY, id);
        end

        //operator station
        else if itemTypeStr = ITEM_TYPE_OPERATOR_STATION then
        begin
          newTrackItem := TOperatorStation.Create(aCanvas, aRasterX, aRasterY, id);
        end

        //unknown item
        else
        begin
          parseLog.Add('  Unknown item type: "' + itemTypeStr + '". Item skipped.');
          continue;
        end;

        if ( newTrackItem.LoadFromXml(itemNode) ) then
        begin
          aTrackItems.Add(newTrackItem);
        end
        else begin
          newTrackItem.Destroy;
        end;

      end;
      itemNode := itemNode.NextSibling;
    end; //while
  end;

  LoadTrackSegments(xmlFile, parseLog);

  SaveLog;
  parseLog.Destroy;
end;


function TRailroadYard.FindTrackItem(paItemId: Cardinal): TTrackItem;
var
  i: TListIterator;
  item: TTrackItem;
begin
  result := nil;

  i := TListIterator.Create(aTrackItems);
  while i.HasNext do
  begin
    item := TTrackItem(i.Next);
    if item.Id = paItemId then
    begin
      result := item;
      break;
    end;
  end;

  i.Destroy;
end;


function TRailroadYard.FindTrackSegment(paSegmentId: Cardinal): TTRackSegment;
var
  i: TListIterator;
  segment: TTrackSegment;
begin
  result := nil;

  i := TListIterator.Create(aTrackSegments);
  while i.HasNext do
  begin
    segment := TTrackSegment(i.Next);
    if segment.Id = paSegmentId then
    begin
      result := segment;
      break;
    end;
  end;

  i.Destroy;
end;


procedure TRailroadYard.LeftMouseClicked(paX, paY: Cardinal);
var
  i: TListIterator;
  item: TTrackItem;
  segId: Cardinal;
  segment: TTrackSegment;
  x, y: real;
begin
  segId := 0;

  x := paX / aRasterX;
  y := paY / aRasterY;

  i := TListIterator.Create(aTrackItems);

  while i.HasNext do
  begin
    item := TTrackItem(i.Next);

    if ((item is TRail) or (item is TSwitch)) and (item.IsOnXY(x,y)) then
    begin
      segId := item.Segment;
      break;
    end;
  end;

  i.Destroy;

  if segId <> 0 then
  begin
    segment:= FindTrackSegment(segId);

    if segment <> nil then
    begin
      segment.SetOccupied(not segment.Occupied);

    end;
  end;

  Draw;
end;


procedure TRailroadYard.MiddleMouseClicked(paX, paY: Cardinal);
begin

end;


procedure TRailroadYard.RightMouseClicked(paX, paY: Cardinal);
var
  i: TListIterator;
  item: TTrackItem;
  x, y: real;
begin
  x := paX / aRasterX;
  y := paY / aRasterY;

  i := TListIterator.Create(aTrackItems);

  while i.HasNext do
  begin
    item := TTrackItem(i.Next);

    if (item is TSwitch) and (item.IsOnXY(x,y)) then
    begin
      TSwitch(item).SwitchPosition;
    end;
  end;

  i.Destroy;

  Draw;
end;


function TRailroadYard.IsSegmentOccupied(paSegment: Cardinal): boolean;
var
  segment: TTrackSegment;
begin
  segment := FindTrackSegment(paSegment);
  if segment <> nil then
    result := segment.Occupied
  else
    result := false;
end;


end.
